use crate::models::{
    Entity, EntityRef, FilterBy, Query, Response, Service, ServiceStorage, Unregister,
};
use actix_web::{HttpRequest, get, post, web};
use std::{collections::HashSet, str::FromStr};
use uuid::Uuid;

#[post("/register")]
pub async fn view_register(
    storage: web::Data<ServiceStorage>,
    service: web::Json<Service>,
    req: HttpRequest,
) -> Response<Uuid> {
    let mut state = storage.lock().unwrap();
    let socket_addr = req.peer_addr().unwrap();
    let service = Entity::from(service.clone(), socket_addr.ip(), socket_addr.port());
    let uuid = service.uid;

    match state.register(service) {
        Ok(_) => Response::with(0, None, Some(uuid)),
        Err(e) => Response::failure(1, Some(e)),
    }
}

#[get("/query")]
pub async fn view_query(
    storage: web::Data<ServiceStorage>,
    query: web::Query<Query>,
) -> Response<Vec<EntityRef>> {
    let mut state = storage.lock().unwrap();
    let mut vec = HashSet::new();
    // call different function to filter from db
    let result = match query.filter_by {
        FilterBy::Group => state.query_with_group(&query.key),
        FilterBy::Name => state.query_with_name(&query.key),
        FilterBy::Uuid => {
            // get uuid from string
            let uuid = Uuid::from_str(&query.key).unwrap();
            // wrap Option<&Arc<...>> into Vec
            state.query_with_uid(uuid).and_then(|e| {
                vec.insert(e);
                Some(&vec)
            })
        }
    };

    // process result of filtering, and serialize them and return as response
    let result = result.map(|vec| vec.iter().map(|arc| EntityRef::clone(&arc)).collect());

    Response::with(0, None, result)
}

#[post("/unregister")]
pub async fn view_unregister(
    storage: web::Data<ServiceStorage>,
    query: web::Query<Unregister>,
) -> Response<Uuid> {
    let mut state = storage.lock().unwrap();
    match state.unregister(query.uid) {
        // return the current uid if success
        Ok(_) => Response::ok(query.uid),
        // return nil uuid if failed
        Err(e) => Response::with(1, Some(e), Some(Uuid::nil())),
    }
}

#[get("/all")]
pub async fn view_get_all(storage: web::Data<ServiceStorage>) -> Response<Option<Vec<EntityRef>>> {
    let state = storage.lock().unwrap();
    let response = state.all_entities();
    Response::ok(response)
}
